#define WIN32_LEAN_AND_MEAN
#undef DrawText
#undef LoadImage

#include "../../allegro_backend.h"
#include <GTL/external_libs/tinynet/tinynet.h>
#include <eastl/algorithm.h>
#include <eastl/sort.h>

#include <map>

#include "Game.h"

struct Value
{
	enum
	{
		TYPE_NIL,
		TYPE_INT,
		TYPE_DOUBLE,
		TYPE_STRING,
		TYPE_POINT,
		TYPE_ARRAY,
		TYPE_MAP
	};
	int Type;

	int64_t IntValue;
	double DoubleValue;
	eastl::string StringValue;
	Point PointValue;

	eastl::vector<Value> ArrayValue;
	typedef eastl::map<eastl::string, Value> map_type;
	map_type MapValue;

	/// remember to reset these after assignment or changing the type
	size_t ArrayIterator;
	map_type::iterator MapIterator;

	Value() : Type(TYPE_NIL) {}
	Value(const Value& other) : Type(other.Type)
	{
		*this = other;
	}
	Value(int i) : Type(TYPE_INT), IntValue(i) {}
	Value(int64_t i) : Type(TYPE_INT), IntValue(i) {}
	Value(double d) : Type(TYPE_DOUBLE), DoubleValue(d) {}
	Value(float d) : Type(TYPE_DOUBLE), DoubleValue(d) {}
	Value(eastl::string d) : Type(TYPE_STRING), StringValue(d), ArrayIterator(0) {}
	Value(const char* d) : Type(TYPE_STRING), StringValue(d), ArrayIterator(0) {}
	Value(Point point) : Type(TYPE_POINT), PointValue(point) {}

	static Value Array()
	{
		Value v;
		v.Type = TYPE_ARRAY;
		v.ArrayIterator = 0;
		return v;
	}

	static Value Map()
	{
		Value v;
		v.Type = TYPE_MAP;
		v.MapIterator = v.MapValue.begin();
		return v;
	}

	Value& operator=(const Value& other)
	{
		if (other.Type != TYPE_MAP)
			MapValue.clear();
		if (other.Type != TYPE_ARRAY)
			ArrayValue.clear();
		if (other.Type != TYPE_STRING)
			StringValue.clear();

		Type = other.Type;
		
		switch (other.Type)
		{
		case TYPE_INT:
			IntValue = other.IntValue;
			break;
		case TYPE_DOUBLE:
			DoubleValue = other.DoubleValue;
			break;
		case TYPE_STRING:
			StringValue = other.StringValue;
			break;
		case TYPE_POINT:
			PointValue = other.PointValue;
			break;
		case TYPE_ARRAY:
			ArrayValue = other.ArrayValue;
			ArrayIterator = 0;
			break;
		case TYPE_MAP:
			MapValue = other.MapValue;
			MapIterator = MapValue.begin();
			break;
		}
		return *this;
	}

	bool operator==(const Value& other) const
	{
		if (Type != other.Type) return false;
		if (this == &other) return true;

		switch (Type)
		{
		case TYPE_NIL:
			return true;
		case TYPE_INT:
			return IntValue == other.IntValue;
		case TYPE_DOUBLE:
			return DoubleValue == other.DoubleValue;
		case TYPE_STRING:
			return StringValue == other.StringValue;
		case TYPE_POINT:
			return PointValue.X == other.PointValue.X && PointValue.Y == other.PointValue.Y;
		}
		return false;
	}

	bool operator<(const Value& other) const
	{
		if (Type != other.Type) return Type - other.Type < 0;
		if (this == &other) return false;

		switch (Type)
		{
		case TYPE_NIL:
			return false;
		case TYPE_INT:
			return IntValue < other.IntValue;
		case TYPE_DOUBLE:
			return DoubleValue < other.DoubleValue;
		case TYPE_STRING:
			return StringValue < other.StringValue;
		case TYPE_POINT:
			return PointValue.X < other.PointValue.X || PointValue.Y < other.PointValue.Y;
		}
		return true;
	}

	eastl::string ToString(int base = 10) const
	{
		switch (Type)
		{
		case TYPE_NIL:
			return "nil";
		case TYPE_INT:
			{
				char str[32];
				itoa((int)IntValue, str, base);
				return str;
			}
		case TYPE_DOUBLE:
			{
				char str[32];
				sprintf(str, "%f", IntValue);
				return str;
			}
		case TYPE_STRING:
			return StringValue;
		case TYPE_POINT:
			{
				char str[64];
				sprintf(str, "[%fx%f]", IntValue);
				return str;
			}
		case TYPE_ARRAY:
			return Join();
		case TYPE_MAP:
			return "[map]";
		}
	}

	double ToNumber() const
	{
		switch (Type)
		{
		case TYPE_NIL:
			return 0;
		case TYPE_INT:
			return (double)IntValue;
		case TYPE_DOUBLE:
			return DoubleValue;
		case TYPE_STRING:
			return atof(StringValue.c_str());
		case TYPE_POINT:
			return 0;
		case TYPE_ARRAY:
			return 0;
		case TYPE_MAP:
			return 0;
		}
		return 0;
	}

	eastl::string ToJson() const;
	
	template <typename T>
	static void WritePod(eastl::vector<char>& buffer, T i)
	{
		size_t s = buffer.size();
		buffer.resize(s+sizeof(i));
		T* ptr = (T*)&buffer[s];
		*ptr = i;
	}
	static void WriteString(eastl::vector<char>& buffer, const eastl::string& s)
	{
		WritePod(buffer, s.size());
		buffer.insert(buffer.end(), s.begin(), s.end());
	}

	void ToBinary(eastl::vector<char>& buffer) const
	{
		switch (Type)
		{
		case TYPE_NIL:
			buffer.push_back('0');
			break;
		case TYPE_INT:
			buffer.push_back('i');
			WritePod(buffer, IntValue);
			break;
		case TYPE_DOUBLE:
			buffer.push_back('d');
			WritePod(buffer, DoubleValue);
			break;
		case TYPE_STRING:
			buffer.push_back('s');
			WriteString(buffer, StringValue);
			break;
		case TYPE_POINT:
			buffer.push_back('p');
			WritePod(buffer, PointValue.X);
			WritePod(buffer, PointValue.Y);
			break;
		case TYPE_ARRAY:
			buffer.push_back('a');
			WritePod(buffer, ArrayValue.size());
			for (size_t i=0; i<ArrayValue.size(); i++)
				ArrayValue[i].ToBinary(buffer);
			break;
		case TYPE_MAP:
			buffer.push_back('m');
			WritePod(buffer, MapValue.size());
			for (map_type::const_iterator it = MapValue.begin(); it!=MapValue.end(); it++)
			{
				WriteString(buffer, it->first);
				it->second.ToBinary(buffer);
			}
			break;
		}
	}

	/*
	struct CharIterator
	{
		CharIterator(const char* buf, size_t siz) : mBuffer(buf), mSize(siz) {}
		CharIterator(const CharIterator& other) : mBuffer(other.mBuffer), mSize(other.mSize) {}
		CharIterator& operator=(const CharIterator& other) { mBuffer = other.mBuffer; mSize = other.mSize; return *this; } 

		char& operator*() { return *mBuffer; }
		CharIterator& operator++() { mSize--; mBuffer++; return *this; } /// prefix
		CharIterator operator++(int) { CharIterator r = *this; ++(*this); return r; } /// prefix

		const char* mBuffer;
		size_t mSize;
	};
	*/

	template <typename T>
	static void ReadPod(const char*& buffer, T& i)
	{
		T* ptr = (T*)buffer;
		i = *ptr;
		buffer += sizeof(T);
	}
	static void ReadString(const char*& buffer, eastl::string& s)
	{
		size_t size = 0;
		ReadPod(buffer, size);
		s.assign(buffer, buffer+size);
		buffer += size;
	}

	void FromBinary(const char*& buffer)
	{
		switch (*buffer++)
		{
		case '0':
			Type = TYPE_NIL;
			break;
		case 'i':
			Type = TYPE_INT;
			ReadPod(buffer, IntValue);
			break;
		case 'd':
			Type = TYPE_DOUBLE;
			ReadPod(buffer, DoubleValue);
			break;
		case 's':
			Type = TYPE_STRING;
			ReadString(buffer, StringValue);
			break;
		case 'p':
			Type = TYPE_POINT;
			ReadPod(buffer, PointValue.X);
			ReadPod(buffer, PointValue.Y);
			break;
		case 'a':
			Type = TYPE_ARRAY;
			{
				size_t size = 0;
				ReadPod(buffer, size);
				ArrayValue.resize(size);
				for (size_t i=0; i<size; i++)
					ArrayValue[i].FromBinary(buffer);
			}
			break;
		case 'm':
			Type = TYPE_MAP;
			{
				size_t size = 0;
				ReadPod(buffer, size);
				for (size_t i=0; i<size; i++)
				{
					eastl::string key;
					ReadString(buffer, key);
					MapValue[key].FromBinary(buffer);
				}
			}
			break;
		}

		if (Type != TYPE_MAP)
			MapValue.clear();
		if (Type != TYPE_ARRAY)
			ArrayValue.clear();
		if (Type != TYPE_STRING)
			StringValue.clear();
	}

	///************************************************************************/
	/// Iteration
	///************************************************************************/

	void Start()
	{
		switch (Type)
		{
		case TYPE_ARRAY:
			ArrayIterator = 0;
			break;
		case TYPE_MAP:
			MapIterator = MapValue.begin();
			break;
		}
	}

	bool Done()
	{
		switch (Type)
		{
		case TYPE_ARRAY:
			return ArrayValue.size() == ArrayIterator;
		case TYPE_MAP:
			return MapIterator == MapValue.end();
		}
		return true;
	}

	void Next()
	{
		switch (Type)
		{
		case TYPE_ARRAY:
			ArrayIterator++;
			break;
		case TYPE_MAP:
			MapIterator++;
			break;
		}
	}

	Value& It()
	{
		switch (Type)
		{
		case TYPE_ARRAY:
			return ArrayValue[ArrayIterator];
		case TYPE_MAP:
			return MapIterator->second;
		}
		return *this;
	}

	///************************************************************************/
	/// String Utilities
	///************************************************************************/

	static eastl::string Printf(const char* fmt, ...) 
	{
		int size=100;
		eastl::string str;
		va_list ap;

		for (;;)
		{
			str.resize(size);
			va_start(ap, fmt);
			int n = vsnprintf((char *)str.c_str(), size, fmt, ap);
			va_end(ap);
			if (n > -1 && n < size)
				return str;
			if (n > -1)
				size=n+1;
			else
				size*=2;
		}
	}

	
	///************************************************************************/
	/// Methods
	///************************************************************************/

	void Push(const Value& v)
	{
		if (TYPE_ARRAY == Type)
			ArrayValue.push_back(v);
	}
	
	Value Pop()
	{
		if (TYPE_ARRAY != Type) return *this;

		Value v = ArrayValue.back();
		ArrayValue.pop_back();
		return v;
	}
	
	void Append(const Value& other, int at = -1)
	{
		if (other.Type == TYPE_ARRAY && Type == TYPE_ARRAY)
		{
			if (at == -1)
				at = ArrayValue.size();
			ArrayValue.insert(ArrayValue.begin()+at, other.ArrayValue.begin(), other.ArrayValue.end());
		}
		else if (other.Type == TYPE_MAP && Type == TYPE_MAP)
		{
			MapValue.insert(other.MapValue.begin(), other.MapValue.end());
		}
		else if (other.Type == TYPE_STRING && Type == TYPE_STRING)
		{
			if (at == -1)
				at = StringValue.size();
			StringValue.insert(	at, other.StringValue);
		}
	}

	void Append(const char* str, int at = -1)
	{
		if (Type != TYPE_STRING) return;
		if (at == -1)
			at = StringValue.size();
		StringValue.insert(at, str);
	}
	void Append(const eastl::string& str, int at = -1)
	{
		Append(str.c_str(), at);
	}
	
	int Find(const Value& key, int start = 0) const
	{
		if (Type == TYPE_ARRAY)
			for (size_t i=start; i<ArrayValue.size(); i++)
				if (ArrayValue[i] == key)
					return i;
		return -1;
	}

	int Find(const char* v, int start = 0)
	{
		if (Type == TYPE_STRING)
			return StringValue.find(v, start);
		else if (Type == TYPE_ARRAY)
			for (size_t i=start; i<ArrayValue.size(); i++)
				if (ArrayValue[i].Type == TYPE_STRING && ArrayValue[i].StringValue == v)
					return i;
		return -1;
	}

	int Find(const eastl::string& v, int start = 0)
	{
		return Find(v.c_str(), start);
	}
	
	int ReverseFind(const Value& key, int start = -1) const
	{
		if (Type != TYPE_ARRAY) return -1;

		start = (start!=-1)?start:ArrayValue.size();
		for (int i=start; i>=0; i--)
			if (ArrayValue[i] == key)
				return i;
		return -1;
	}

	eastl::string Join(const char* separator = ",") const
	{
		if (Type != TYPE_ARRAY) return "";

		eastl::string ret = "";
		for (size_t i=0; i<ArrayValue.size(); i++)
		{
			if (i) ret += separator;
			ret += ArrayValue[i].ToString();
		}
		return ret;
	}

	void Reverse()
	{
		if (Type == TYPE_ARRAY)
			eastl::reverse(ArrayValue.begin(), ArrayValue.end());
		else if (Type == TYPE_STRING)
			eastl::reverse(StringValue.begin(), StringValue.end());
	}

	Value Slice(size_t start, size_t end) const
	{
		if (Type == TYPE_ARRAY)
		{
			Value ret = Value::Array();
			ret.ArrayValue.assign(ArrayValue.begin()+start, ArrayValue.begin()+end);
			return ret;
		}
	}

	eastl::string SubstringTo(size_t start, size_t end) const
	{
		if (Type != TYPE_STRING) return "";

		return StringValue.substr(start, end-start);
	}
	
	eastl::string SubstringLen(size_t start, size_t len) const
	{
		if (Type != TYPE_STRING) return "";

		return StringValue.substr(start, len);
	}

	void Trim()
	{
		if (Type != TYPE_STRING) return;

		size_t st = StringValue.find_first_not_of(" \t\r\n");
		if (st == eastl::string::npos) { StringValue = ""; return; }
		size_t end = StringValue.find_last_not_of(" \t\r\n");
		StringValue = StringValue.substr(st, end);
	}

	void Sort()
	{
		if (Type == TYPE_ARRAY)
			eastl::sort(ArrayValue.begin(), ArrayValue.end());
		else if (Type == TYPE_STRING)
			eastl::sort(StringValue.begin(), StringValue.end());
	}

	void Insert(int at, const Value& val)
	{
		if (Type == TYPE_ARRAY)
			ArrayValue.insert(ArrayValue.begin()+at, val);
		else if (Type == TYPE_STRING && val.Type == TYPE_STRING)
			StringValue.insert(at, val.StringValue);
	}

	void Insert(int at, const char* val)
	{
		if (Type == TYPE_ARRAY)
			ArrayValue.insert(ArrayValue.begin()+at, Value(val));
		else if (Type == TYPE_STRING)
			StringValue.insert(at, val);
	}

	void Insert(int at, const eastl::string& val)
	{
		Insert(at, val.c_str());
	}

	struct cstrcompare
	{
		bool operator()(const eastl::string& a, const char* b)
		{
			return a < b;
		}
		bool operator()(const char* b, const eastl::string& a)
		{
			return a < b;
		}
	};

	void Erase(const char* key)
	{
		if (Type == TYPE_MAP)
		{
			map_type::iterator it = MapValue.find_as(key, cstrcompare());
			if (it != MapValue.end())
				MapValue.erase(it);
		}
	}

	void Erase(const eastl::string& key)
	{
		if (Type == TYPE_MAP)
			MapValue.erase(key);
	}

	bool Has(const char* key) const
	{
		if (Type == TYPE_MAP)
		{
			map_type::const_iterator it = MapValue.find_as(key, cstrcompare());
			return (it != MapValue.end());
		}
	}

	bool Has(const eastl::string& key) const
	{
		if (Type == TYPE_MAP)
			return MapValue.find(key) != MapValue.end();
	}

	Value GetKeys() const
	{
		Value v = Value::Array();
		for (map_type::const_iterator it=MapValue.begin(); it!=MapValue.end(); it++)
			v.Push(it->first);
		return v;
	}

	Value GetValues() const
	{
		Value v = Value::Array();
		for (map_type::const_iterator it=MapValue.begin(); it!=MapValue.end(); it++)
			v.Push(it->second);
		return v;
	}

	void Clear()
	{
		switch (Type)
		{
		case TYPE_ARRAY:
			ArrayValue.clear();
			ArrayIterator = 0;
			return;
		case TYPE_MAP:
			MapValue.clear();
			MapIterator = MapValue.begin();
			return;
		case TYPE_STRING:
			StringValue.clear();
			ArrayIterator = 0;
			return;
		}
	}

	void EraseTo(int from, int to = -1)
	{
		if (Type == TYPE_ARRAY)
		{
			if (to == -1) to = ArrayValue.size();
			ArrayValue.erase(ArrayValue.begin()+from, ArrayValue.begin()+to);
		}
		else if (Type == TYPE_STRING)
		{
			if (to == -1) to = StringValue.size();
			StringValue.erase(StringValue.begin()+from, StringValue.begin()+to);
		}
	}

	void EraseLen(int from, int len)
	{
		EraseTo(from, from+len);
	}

	Value Split(const eastl::string& separator) const
	{
		return Split(separator.c_str());
	}

	Value Split(const char* separator) const
	{
		Value result = Value::Array();
		size_t  start = 0, end = 0;

		size_t delimsize = strlen(separator);

		while (end != eastl::string::npos)
		{
			end = StringValue.find(separator, start);

			result.Push( Value(StringValue.substr( start, (end == eastl::string::npos) ? eastl::string::npos : end - start)) );

			start = ( ( end > (eastl::string::npos - delimsize) ) ?  eastl::string::npos  :  end + delimsize);
		}

		return result;
	}

	static char __tolower(char i) { return (char)::tolower(i); }
	static char __toupper(char i) { return (char)::toupper(i); }

	eastl::string ToLower() const
	{
		if (Type != TYPE_STRING) return "";
		eastl::string ret = StringValue;
		eastl::transform(ret.begin(), ret.end(), ret.begin(), __tolower);
		return ret;
	}

	eastl::string ToUpper() const
	{
		if (Type != TYPE_STRING) return "";
		eastl::string ret = StringValue;
		eastl::transform(ret.begin(), ret.end(), ret.begin(), __toupper);
		return ret;
	}

	size_t Len() const
	{
		switch (Type)
		{
		case TYPE_ARRAY:
			return ArrayValue.size();
		case TYPE_STRING:
			return StringValue.size();
		case TYPE_MAP:
			return MapValue.size();
		default:
			return 0;
		}
	}

	Value& operator[] (int index) {
		return ArrayValue[index];
	}
	Value& operator[] (const eastl::string& str) {
		return MapValue[str];
	}
	Value& operator[] (const char* str) {
		map_type::iterator it = MapValue.find_as(str, cstrcompare());
		if (it == MapValue.end())
			return MapValue[str];
		else
			return it->second;
	}
};

int main()
{
	Value v = Value::Array();
	v.Push(10);
	v.Push("asdf");
	Value m = Value::Map();
	m["woo"] = 12;
	m["wootang"] = Point(12, 14);
	v.Push(m);
	eastl::vector<char> buf;
	v.ToBinary(buf);
	
	FILE* f = fopen("Data/save.txt", "wb");
	fwrite(buf.data(), buf.size(), 1, f);
	fclose(f);

	//Logging::Level = Logging::Trace;
	GTL::Engine::Backend = new AllegroBackend;
	SummonerGame game;
	GTL::Engine::Backend->Init(&game, 1024, 700, false);
	game.Run();

	return 0;
}